package pl.skubicha.controller;

import java.util.ArrayList;
import java.util.List;

import javax.validation.Valid;

import org.apache.commons.collections.CollectionUtils;
import org.jboss.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import pl.skubicha.domain.CreditingForm;
import pl.skubicha.domain.FinalGrades;
import pl.skubicha.domain.PartialGrades;
import pl.skubicha.domain.Subjects;
import pl.skubicha.domain.Users;
import pl.skubicha.exceptions.GradeNotFoundException;
import pl.skubicha.exceptions.UserGotAlreadyGradeForSubjectException;
import pl.skubicha.exceptions.UserNotFoundException;
import pl.skubicha.response.GradesDTO;
import pl.skubicha.response.GradesForUserDTO;
import pl.skubicha.response.GroupDTO;
import pl.skubicha.response.MyGradesDTO;
import pl.skubicha.response.StudentsForGroupsDTO;
import pl.skubicha.response.SubjectForGroupsDTO;
import pl.skubicha.service.CreditingFormService;
import pl.skubicha.service.GradesService;
import pl.skubicha.service.GroupsService;
import pl.skubicha.service.SubjectService;
import pl.skubicha.service.UserService;
import pl.skubicha.utils.FinalGradesMapper;
import pl.skubicha.utils.GradesForUserMapper;
import pl.skubicha.utils.GroupMapper;
import pl.skubicha.utils.PartialGradesMapper;
import pl.skubicha.utils.SubjectMapper;
import pl.skubicha.utils.UserMapper;
import pl.skubicha.utils.UserSubjectsGroupsSelection;

@Controller
@RequestMapping( { "/grades" } )
@PreAuthorize("hasAnyRole('Wykładowca','Student')")
public class GradesController {

	private static final String FINAL_GRADE_TYPE = "final";

	private static Logger logger = Logger.getLogger( GradesController.class );

	@Autowired
	private GroupsService groupService;

	@Autowired
	private UserService userService;

	@Autowired
	private SubjectService subjectService;

	@Autowired
	@Qualifier(value="partialGradesRepositoryService")
	private GradesService<PartialGrades> partialGradesService;
	
	@Autowired
	@Qualifier(value="finalGradesRepositoryService")
	private GradesService<FinalGrades> finalGradesService;
	
	@Autowired
	private CreditingFormService formService;

	private List< SubjectForGroupsDTO > subjects;

	@ModelAttribute( "allGroups" )
	public List< GroupDTO > getAllGroups() {
		return GroupMapper.map( groupService.findAll() );
	}

	@ModelAttribute( "allStudentsInGroup" )
	public List< GroupDTO > getAllStudentsInGroup() {
		return GroupMapper.map( groupService.findAll() );
	}
	
	@ModelAttribute( "allCreditingForms" )
	public List< CreditingForm > getAllCreditingForms() {
		return formService.findAll();
	}

	@RequestMapping( value = "/students", method = RequestMethod.GET, produces = "application/json" )
	public @ResponseBody
	List< StudentsForGroupsDTO > findAllStudentsInGroup( @RequestParam( value = "id", required = true ) Integer id ) {
		return UserMapper.mapStudentsToGroupsDTO( userService.findAllStudentsInGroup( id ) );
	}

	@RequestMapping( value = "/groups", method = RequestMethod.GET, produces = "application/json" )
	public @ResponseBody
	List< GroupDTO > findAllGroups() {
		return GroupMapper.map( groupService.findAll() );
	}

	@RequestMapping( value = "/subjects", method = RequestMethod.GET, produces = "application/json" )
	public @ResponseBody
	List< SubjectForGroupsDTO > findAllSubjectsInGroup( @RequestParam( value = "id", required = true ) Integer id ) {
		return subjects = SubjectMapper.mapSubjectsToGroupsDTO( subjectService.findAllSubjectsForGroup( id ) );
	}

	@RequestMapping( value = "/mygrades", method = RequestMethod.GET )
	public String getMyGrades( final ModelMap model ) {

		try {
			Users user = userService.findByLogin( SecurityContextHolder.getContext().getAuthentication().getName() );
			
			List<MyGradesDTO> myGrades = new ArrayList< MyGradesDTO >();
			int size=0;
			
			for(Subjects subject : user.getGroups().getSubjects()){
				MyGradesDTO dto = new MyGradesDTO();
				dto.setSubjectName( subject.getSubjectName() );
				dto.setTeacherName( subject.getUsers().getFullName() );
				
				for(PartialGrades partialGrades : subject.getPartialGrades()){
					if(partialGrades.getUsers().getId().compareTo( user.getId() ) == 0){
						dto.getPartialGrades().add( partialGrades );
						if(dto.getPartialGrades().size() > size){
							size = dto.getPartialGrades().size();
						}
					}
				}	
				
				if(dto.getPartialGrades().size() == 0){
					dto.getPartialGrades().add( new PartialGrades() );
				}
								
				for(FinalGrades finalGrades : subject.getFinalGrades()){
					if(finalGrades.getUsers().getId().compareTo( user.getId() ) == 0){
						dto.getFinalGrades().add( finalGrades );
					}
				}	
				
				if(dto.getFinalGrades().size() == 0){
					dto.getFinalGrades().add( new FinalGrades() );
				}
				myGrades.add( dto );
			}			
			
			for(MyGradesDTO dto : myGrades){
				if(dto.getPartialGrades().size() < size){
					while ( size > dto.getPartialGrades().size() ) {
						dto.getPartialGrades().add( new PartialGrades() );
					}
					
				}
			}
			
			model.addAttribute( "myGrades", myGrades );
			model.addAttribute( "size", size);
		} catch ( UserNotFoundException e ) {
		}

		return "grades/myGrades";
	}

	@RequestMapping( value = "/show", method = RequestMethod.POST )
	public String getStudentsGrades( final UserSubjectsGroupsSelection userSubjectsGroupsSelection, final ModelMap model ) {

		Integer idGroup = userSubjectsGroupsSelection.getSelectedGroup();
		Integer idSubject = userSubjectsGroupsSelection.getSelectedSubject();

		if(idGroup != null && idSubject != null){
			List< PartialGrades > partialGrades = partialGradesService.findAllGradesForSubjectInGroup( idSubject, idGroup );
			List< FinalGrades > finalGrades = finalGradesService.findAllGradesForSubjectInGroup( idSubject, idGroup );
			
			List< Users > findAllStudentsInGroup = userService.findAllStudentsInGroup( idGroup );
			List< Subjects > allSubjectsInGroup = subjectService.findAllSubjectsForGroup( idGroup );
			List< Users > findAllStudentsInGroupAndSubject = new ArrayList< Users >();
			int maxGrades = 0;
			
			for ( Users user : findAllStudentsInGroup ) {
				for ( Subjects subject : allSubjectsInGroup ) {				
					if ( idSubject.compareTo( subject.getId() ) == 0 ) {
						if(user.getPartialGrades().size() > maxGrades){
							maxGrades = user.getPartialGrades().size() ;
						}
						findAllStudentsInGroupAndSubject.add( user );
					}
				}
			}
	
			List<GradesForUserDTO> gradesForUsers = GradesForUserMapper.map( findAllStudentsInGroupAndSubject, maxGrades );
	
			model.addAttribute( "maxGrades", maxGrades );
			model.addAttribute( "gradesForUsers", gradesForUsers );
			model.addAttribute( "partialGradesDTO", PartialGradesMapper.map( partialGrades ) );
			model.addAttribute( "finalGradesDTO",FinalGradesMapper.map( finalGrades ) );
			model.addAttribute( "subjectAndGroupsSelection", userSubjectsGroupsSelection );
			model.addAttribute( "subjects", subjects );
		}
		return "grades/list";
	}

	@RequestMapping( { "", "/" } )
	public String defaultPage( final ModelMap model ) {
		model.addAttribute( "userSubjectsGroupsSelection", new UserSubjectsGroupsSelection() );
		model.addAttribute( "subjects", subjects );
		return "grades/list";
	}

	@RequestMapping( value = { "/create/{type}" }, method = RequestMethod.GET )
	@PreAuthorize( "hasRole('Wykładowca')" )
	public String create( @PathVariable( value = "type" ) final String type, final ModelMap model ) {

		UserSubjectsGroupsSelection userSubjectsGroupsSelection = new UserSubjectsGroupsSelection();
 
		List< GroupDTO > allGroups = findAllGroups();
		if ( CollectionUtils.isNotEmpty( allGroups ) ) {
			userSubjectsGroupsSelection.setSelectedGroup( allGroups.get( 0 ).getId() );

			List< SubjectForGroupsDTO > allSubjects = findAllSubjectsInGroup( allGroups.get( 0 ).getId() );
			userSubjectsGroupsSelection.setSelectedSubject( allSubjects.get( 0 ).getId() );
			model.addAttribute( "subjects", allSubjects );

			List< StudentsForGroupsDTO > allStudents = findAllStudentsInGroup( allGroups.get( 0 ).getId() );
			userSubjectsGroupsSelection.setSelectedUser( allStudents.get( 0 ).getId() );
			model.addAttribute( "students", allStudents );
		}

		model.addAttribute( "gradesDTO", new GradesDTO( userSubjectsGroupsSelection ) );
		model.addAttribute( "type", "create" );
		model.addAttribute( "gradeType", type );
		return "grades/edit";
	}

	@RequestMapping( value = { "/create/{type}" }, method = RequestMethod.POST )
	@PreAuthorize( "hasRole('Wykładowca')" )
	public String create( @Valid final GradesDTO gradesDTO, BindingResult bindingResult, @PathVariable( value = "type" ) final String type, final ModelMap model ) {
		
		if ( bindingResult.hasErrors() ) {
			
			UserSubjectsGroupsSelection userSubjectsGroupsSelection = new UserSubjectsGroupsSelection();

			List< GroupDTO > allGroups = findAllGroups();
			if ( CollectionUtils.isNotEmpty( allGroups ) ) {
				userSubjectsGroupsSelection.setSelectedGroup( allGroups.get( 0 ).getId() );

				List< SubjectForGroupsDTO > allSubjects = findAllSubjectsInGroup( allGroups.get( 0 ).getId() );
				userSubjectsGroupsSelection.setSelectedSubject( allSubjects.get( 0 ).getId() );
				model.addAttribute( "subjects", allSubjects );

				List< StudentsForGroupsDTO > allStudents = findAllStudentsInGroup( allGroups.get( 0 ).getId() );
				userSubjectsGroupsSelection.setSelectedUser( allStudents.get( 0 ).getId() );
				model.addAttribute( "students", allStudents );
			}
			model.addAttribute( "type", "create" );
			model.addAttribute( "gradeType", type );
			return "grades/edit";
		}
		
		try {
			if(FINAL_GRADE_TYPE.equals( type )){
				checkIfStudentGotAlreadyGrade( gradesDTO );				
			}
			
			Users user = userService.findById( gradesDTO.getUserSubjectsGroupsSelection().getSelectedUser() );
			CreditingForm form = formService.findById( gradesDTO.getUserSubjectsGroupsSelection().getSelectedForm() );

			gradesDTO.setSubjects( subjectService.findById( gradesDTO.getUserSubjectsGroupsSelection().getSelectedSubject() ) );
			gradesDTO.setUsers( user );
			gradesDTO.setForm( form );
			
			if(FINAL_GRADE_TYPE.equals( type )){
				FinalGrades created = finalGradesService.create( gradesDTO );
				user.updateGrade( created );			
			}else{
				PartialGrades created = partialGradesService.create( gradesDTO );
				user.updateGrade( created );				
			}
			userService.create( user );

			model.addAttribute( "status", "grades.create.success" );
		} catch ( final Exception ex ) {
			logger.debug( ex );
			model.addAttribute( "status", "grades.create.failure" );
		}

		model.addAttribute( "userSubjectsGroupsSelection", new UserSubjectsGroupsSelection() );
		model.addAttribute( "subjects", subjects );
		model.addAttribute( "gradesDTO", new ArrayList< GradesDTO >() );

		return "grades/list";
	}

	@RequestMapping( value = { "/edit/{type}/{id}" }, method = RequestMethod.GET )
	@PreAuthorize( "hasRole('Wykładowca')" )
	public String edit( @PathVariable( value = "type" ) final String type, @PathVariable( value = "id" ) final Integer id, final ModelMap model ) {

		try {
			
			PartialGrades partialGrades =null;
			FinalGrades finalGrades =null;
			GradesDTO gradesDTO = null;
			
			if(FINAL_GRADE_TYPE.equals( type )){
				finalGrades = finalGradesService.findById( id );
				gradesDTO = FinalGradesMapper.map( finalGrades );
			}else{
				partialGrades = partialGradesService.findById( id );
				gradesDTO = PartialGradesMapper.map( partialGrades );
			}
						 			
			UserSubjectsGroupsSelection userSubjectsGroupsSelection = new UserSubjectsGroupsSelection();

			List< GroupDTO > allGroups = findAllGroups();
			if ( CollectionUtils.isNotEmpty( allGroups ) ) {
				userSubjectsGroupsSelection.setSelectedGroup( gradesDTO.getUsers().getGroups().getId() );

				List< SubjectForGroupsDTO > allSubjects = findAllSubjectsInGroup( allGroups.get( 0 ).getId() );
				userSubjectsGroupsSelection.setSelectedSubject( gradesDTO.getSubjects().getId() );
				model.addAttribute( "subjects", allSubjects );

				List< StudentsForGroupsDTO > allStudents = findAllStudentsInGroup( allGroups.get( 0 ).getId() );
				userSubjectsGroupsSelection.setSelectedUser( gradesDTO.getUsers().getId() );
				model.addAttribute( "students", allStudents );
				gradesDTO.setUserSubjectsGroupsSelection( userSubjectsGroupsSelection );
			}
			
			model.addAttribute( "gradesDTO", gradesDTO );
			model.addAttribute( "type", "edit" );
			model.addAttribute( "gradeType", type );
		} catch ( final GradeNotFoundException ex ) {
			logger.debug( ex );
			model.addAttribute( "status", "grades.not.found" );
			return "grades/list";
		}

		return "grades/edit";
	}
	
	@RequestMapping( value = { "/edit/{type}" }, method = RequestMethod.POST )
	@PreAuthorize( "hasRole('Wykładowca')" )
	public String update( @PathVariable( value = "type" ) final String type, @Valid final GradesDTO gradesDTO, BindingResult bindingResult, final ModelMap model ) {
		
		if ( bindingResult.hasErrors() ) {
			model.addAttribute( "type", "edit" );
			model.addAttribute( "gradeType", type );
			
			List< GroupDTO > allGroups = findAllGroups();
			if ( CollectionUtils.isNotEmpty( allGroups ) ) {
				List< SubjectForGroupsDTO > allSubjects = findAllSubjectsInGroup( allGroups.get( 0 ).getId() );
				model.addAttribute( "subjects", allSubjects );

				List< StudentsForGroupsDTO > allStudents = findAllStudentsInGroup( allGroups.get( 0 ).getId() );
				model.addAttribute( "students", allStudents );
			}
			
			return "grades/edit";
		}
		
		
		try {
			Users user = userService.findById( gradesDTO.getUserSubjectsGroupsSelection().getSelectedUser() );
			gradesDTO.setSubjects( subjectService.findById( gradesDTO.getUserSubjectsGroupsSelection().getSelectedSubject() ) );
			gradesDTO.setUsers( user );
			
			if(FINAL_GRADE_TYPE.equals( type )){
				FinalGrades updatedGrade = finalGradesService.update( gradesDTO );
				user.updateGrade( updatedGrade );
			}else{
				PartialGrades updatedGrade = partialGradesService.update( gradesDTO );
				user.updateGrade( updatedGrade );
			}
						
			userService.create( user );
			model.addAttribute( "status", "grades.update.success" );
		} catch ( final Exception ex ) {
			logger.debug( ex );
			model.addAttribute( "status", "grades.update.failure" );
		}

		model.addAttribute( "userSubjectsGroupsSelection", new UserSubjectsGroupsSelection() );
		model.addAttribute( "subjects", subjects );
		model.addAttribute( "gradesDTO", new ArrayList< GradesDTO >() );

		return "grades/list";
	}

	@RequestMapping( value = "/delete/{type}/{id}" )
	@PreAuthorize( "hasRole('Wykładowca')" )
	public String delete( @PathVariable( value = "type" ) final String type, @PathVariable( value = "id" ) final Integer id, final ModelMap model ) {
		try {
			
			if(FINAL_GRADE_TYPE.equals( type )){
				finalGradesService.delete( id );
			}else{
				partialGradesService.delete( id );
			}			
			
			model.addAttribute( "status", "grades.delete.success" );
		} catch ( final GradeNotFoundException ex ) {
			model.addAttribute( "status", "grades.not.found" );
			logger.debug( ex );
		}

		model.addAttribute( "userSubjectsGroupsSelection", new UserSubjectsGroupsSelection() );
		model.addAttribute( "subjects", subjects );
		model.addAttribute( "gradesDTO", new ArrayList< GradesDTO >() );

		return "grades/list";
	}

	private void checkIfStudentGotAlreadyGrade( final GradesDTO toCreate ) throws UserNotFoundException, UserGotAlreadyGradeForSubjectException {
		Integer selectedUser = toCreate.getUserSubjectsGroupsSelection().getSelectedUser();
		Integer selectedSubject = toCreate.getUserSubjectsGroupsSelection().getSelectedSubject();
		List< FinalGrades > grades = finalGradesService.findAllForUser( selectedUser, selectedSubject );

		if ( CollectionUtils.isNotEmpty( grades ) && toCreate.getId() != null ) {
			for ( FinalGrades grade : grades ) {
				if ( toCreate.getId().compareTo( grade.getId() ) == 0 ) {
					return;
				}
			}
		}

		if ( CollectionUtils.isNotEmpty( grades ) ) {
			throw new UserGotAlreadyGradeForSubjectException();
		}
	}
}